/**
 * Copyright 2009 Marc Stogaitis and Mimi Sun
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.gmote.server;

import java.util.logging.Level;
import java.util.logging.Logger;

import org.gmote.common.Protocol.MouseEvent;
import org.gmote.common.packet.KeyboardEventPacket;
import org.gmote.common.packet.MouseClickPacket;
import org.gmote.common.packet.MouseWheelPacket;

import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.view.IPointerController;
import android.view.IWindowManager;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.MotionEvent.PointerCoords;

/**
 * Handles requests to move the mouse send keyboard commands.
 * @author Marc
 *
 */
public class TrackpadHandler {
  private static final Logger LOGGER = Logger.getLogger(TrackpadHandler.class
      .getName());
  private static TrackpadHandler instance = null;
  private IBinder pointerbinder = ServiceManager.getService( "pointercontroller" );
  private final IPointerController controller = IPointerController.Stub.asInterface( pointerbinder );
  IBinder wmbinder = ServiceManager.getService( "window" );
  final IWindowManager wm = IWindowManager.Stub.asInterface( wmbinder );  
  private boolean buttonDown = false;
  float mouseX = 1;
  float mouseY = 1;
  long lastTime = 0;
  
  /**
   * Private constructor to prevent instantiation.
   */
  private TrackpadHandler() {
	  try {
		  controller.setPresentation(0);
	} catch (RemoteException e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	}
  }
  
  /**
   * Gets an instance of this class.
   */
  public static TrackpadHandler instance() {
    if (instance == null) {
      instance = new TrackpadHandler();
    }
    return instance;
  }
  
  /**
   * Moves the mouse on the screen of a user specified amount.
   */
  public void handleMoveMouseCommand(short diffX, short diffY) {
	float[] x = new float[1];
	float[] y = new float[1];
	long curTime = SystemClock.uptimeMillis();
	try {
		if (curTime - lastTime > 5000) {
			controller.unfade(0);
			controller.getPosition(x, y);
			mouseX = x[0];
			mouseY = y[0];
		}
		mouseX += diffX;
		mouseY += diffY;
		controller.setPosition(mouseX, mouseY);
		if (MouseUtil.buttonDown) {
			MotionEvent e = MotionEvent.obtain(curTime, curTime, MotionEvent.ACTION_MOVE, mouseX, mouseY, 0);
			wm.injectPointerEvent(e, false);
		}
	} catch (RemoteException e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	}
//	  LOGGER.info("handleMoveMouseCommand, dx=" + diffX + ", dy=" + diffY);
    
  }

  /**
   * Performs a mouse click based on what was sent by the user.
   */
  public void hanldeMouseClickCommand(MouseClickPacket packet) {
    MouseEvent mouseEvent = packet.getMouseEvent();
    MouseUtil.doMouseEvent(mouseEvent, wm, controller);
  }
 
  public void handleKeyPressCommand(KeyboardEventPacket packet) {
    int keyCode = packet.getKeyCode();
    if (keyCode < 0) {
      // Handle special characters
      if (keyCode == KeyboardEventPacket.DELETE_KEYCODE) {
        type(new int[] {KeyEvent.KEYCODE_DEL});
      }
      else if (keyCode == KeyboardEventPacket.BACK_KEYCODE) {
        type(new int[] {KeyEvent.KEYCODE_BACK});
      }
      else if (keyCode == KeyboardEventPacket.HOME_KEYCODE) {
        type(new int[] {KeyEvent.KEYCODE_HOME});
      }
      else if (keyCode == KeyboardEventPacket.MENU_KEYCODE) {
        type(new int[] {KeyEvent.KEYCODE_MENU});
      }
    } else {
      char keyChar = (char)keyCode;
      type(keyCodes(keyChar));
    }
    LOGGER.info("handleKeyPressCommand: keycode=" + keyCode);
    
  }
  
  public void handleMouseWheelCommand(MouseWheelPacket packet) {
    LOGGER.info("handleMouseWheelCommand: amount=" + packet.getWheelAmount());
    long curTime = SystemClock.uptimeMillis();
    try {
    	PointerCoords pointerCoords[] = new PointerCoords[1];
    	pointerCoords[0] = new PointerCoords();
    	int[] id = new int[1];
    	id[0] = 0;
    	pointerCoords[0].clear();
        pointerCoords[0].setAxisValue(MotionEvent.AXIS_VSCROLL, packet.getWheelAmount());
        pointerCoords[0].setAxisValue(MotionEvent.AXIS_X, mouseX);
        pointerCoords[0].setAxisValue(MotionEvent.AXIS_Y, mouseY);
        MotionEvent e = MotionEvent.obtain(curTime, curTime, MotionEvent.ACTION_SCROLL, 1, id, pointerCoords,
        		0, 1.0f, 1.0f, 0, 0, 0, 0);
		wm.injectPointerEvent(e, false);
	} catch (RemoteException e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	}
  }
  
  /**
   * Type the codes provided. Multiple codes can result if the character is uppercase
   * or special e.g. SHIFT + a (A) or SHIFT + 1 (!)
   */
  private void type(int[] code) {
    int count = code.length;
    int pos = 0;

    try {
      while (count > 1 && pos < count - 1) {
    	  LOGGER.info("down" + code[pos]);
    	  wm.injectKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, code[pos++]), true);
      }

   	  wm.injectKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, code[pos]), true);
   	  wm.injectKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, code[pos]), true);

      while (count > 1 && pos > 0) {
    	  wm.injectKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, code[--pos]), true);
    	  LOGGER.info("up" + code[pos]);
      }
    } catch (RemoteException e) {
      LOGGER.log(Level.SEVERE, e.getMessage(), e);
    }
  }

  /**
   * Maps the specified character to key code acceptable to the Robot. The keys
   * are mapped corresponding to the code from the
   * <code>java.awt.event.KeyEvent</code> associated with the SHIFT key (if
   * necessary).
   * 
   * @param key
   *          the character code to be converted.
   * 
   * @return the array of key codes. If the character is uppercase or special,
   *         the first code returned in the array is the SHIFT key.
   */
  private int[] keyCodes(char key) {
    int[] codes = new int[0];

    if (key >= '0' && key <= '9') {
      codes = new int[] { Character.getNumericValue(key) + KeyEvent.KEYCODE_0 };
    } else if (key >= 'a' && key <= 'z') {
      codes = new int[] { key - 'a' + KeyEvent.KEYCODE_A };
    } else if (key >= 'A' && key <= 'Z') {
      codes = new int[] { KeyEvent.KEYCODE_SHIFT_LEFT, key - 'A' + KeyEvent.KEYCODE_A };
    } else if (key == '!') {
      codes = new int[] { KeyEvent.KEYCODE_SHIFT_LEFT, KeyEvent.KEYCODE_1 };
    } else if (key == '@') {
      codes = new int[] { KeyEvent.KEYCODE_SHIFT_LEFT, KeyEvent.KEYCODE_2 };
    } else if (key == '#') {
      codes = new int[] { KeyEvent.KEYCODE_SHIFT_LEFT, KeyEvent.KEYCODE_3 };
    } else if (key == '$') {
      codes = new int[] { KeyEvent.KEYCODE_SHIFT_LEFT, KeyEvent.KEYCODE_4 };
    } else if (key == '%') {
      codes = new int[] { KeyEvent.KEYCODE_SHIFT_LEFT, KeyEvent.KEYCODE_5 };
    } else if (key == '^') {
      codes = new int[] { KeyEvent.KEYCODE_SHIFT_LEFT, KeyEvent.KEYCODE_6 };
    } else if (key == '&') {
      codes = new int[] { KeyEvent.KEYCODE_SHIFT_LEFT, KeyEvent.KEYCODE_7 };
    } else if (key == '*') {
      codes = new int[] { KeyEvent.KEYCODE_SHIFT_LEFT, KeyEvent.KEYCODE_8 };
    } else if (key == '(') {
      codes = new int[] { KeyEvent.KEYCODE_SHIFT_LEFT, KeyEvent.KEYCODE_9 };
    } else if (key == ')') {
      codes = new int[] { KeyEvent.KEYCODE_SHIFT_LEFT, KeyEvent.KEYCODE_0 };
    } else if (key == '<') {
      codes = new int[] { KeyEvent.KEYCODE_SHIFT_LEFT, KeyEvent.KEYCODE_COMMA };
    } else if (key == '>') {
      codes = new int[] { KeyEvent.KEYCODE_SHIFT_LEFT, KeyEvent.KEYCODE_PERIOD };
    } else if (key == '?') {
      codes = new int[] { KeyEvent.KEYCODE_SHIFT_LEFT, KeyEvent.KEYCODE_SLASH };
    } else if (key == '|') {
      codes = new int[] { KeyEvent.KEYCODE_SHIFT_LEFT, KeyEvent.KEYCODE_BACKSLASH };
    } else if (key == '_') {
      codes = new int[] { KeyEvent.KEYCODE_SHIFT_LEFT, KeyEvent.KEYCODE_MINUS };
    } else if (key == '+') {
      codes = new int[] { KeyEvent.KEYCODE_SHIFT_LEFT, KeyEvent.KEYCODE_EQUALS };
    } else if (key == '{') {
      codes = new int[] { KeyEvent.KEYCODE_SHIFT_LEFT, KeyEvent.KEYCODE_LEFT_BRACKET };
    } else if (key == '}') {
      codes = new int[] { KeyEvent.KEYCODE_SHIFT_LEFT, KeyEvent.KEYCODE_RIGHT_BRACKET };
    } else if (key == ':') {
      codes = new int[] { KeyEvent.KEYCODE_SHIFT_LEFT, KeyEvent.KEYCODE_SEMICOLON };
    } else if (key == '\"') {
      codes = new int[] { KeyEvent.KEYCODE_SHIFT_LEFT, KeyEvent.KEYCODE_APOSTROPHE };
    } else if (key == '~') {
      codes = new int[] { KeyEvent.KEYCODE_SHIFT_LEFT, KeyEvent.KEYCODE_GRAVE };
    } else {
      codes = new int[] { keyCode(key) };
    }

    return codes;
  }

  /**
   * Maps the specified character to key code acceptable to the Robot. This key
   * can be directly mapped to the code from the
   * <code>java.awt.event.KeyEvent</code> class.
   * 
   * @param key
   *          the character code to be converted.
   * 
   * @return the corresponding code mapped from the
   *         <code>java.awt.event.KeyEvent</code> class.
   */
  private int keyCode(char key) {
    int code;

    switch (key) {
    case '\\':
      code = KeyEvent.KEYCODE_BACKSLASH;
      break;
    case '[':
      code = KeyEvent.KEYCODE_LEFT_BRACKET;
      break;
    case ']':
      code = KeyEvent.KEYCODE_RIGHT_BRACKET;
      break;
    case '.':
      code = KeyEvent.KEYCODE_PERIOD;
      break;
    case '\'':
      code = KeyEvent.KEYCODE_APOSTROPHE;
      break;
    case '/':
      code = KeyEvent.KEYCODE_SLASH;
      break;
    case '-':
      code = KeyEvent.KEYCODE_MINUS;
      break;
    case ',':
      code = KeyEvent.KEYCODE_COMMA;
      break;
    case ';':
      code = KeyEvent.KEYCODE_SEMICOLON;
      break;
    case '\t':
      code = KeyEvent.KEYCODE_TAB;
      break;
    default:
      int keyValue = (int)key;
      if (keyValue == 247) {
        code = KeyEvent.KEYCODE_SLASH;
      } else if (keyValue == 215) {
        code = KeyEvent.KEYCODE_STAR;
      } else if (keyValue == 61) {
        code = KeyEvent.KEYCODE_EQUALS;
      } else if (keyValue == 96) {
        code = KeyEvent.KEYCODE_GRAVE;
      } else if (keyValue == 10) {
        code = KeyEvent.KEYCODE_ENTER;
      } else {
        code = KeyEvent.KEYCODE_SPACE;
      }
      break;
    }

    return code;
  }
  // Implemented by http://www.codeproject.com/KB/cs/runawayapp.aspx
  
}
